/**
 * 200109：更改copy方法为THREE like，并移除重写的clone
 * 200227:减少一个箭头基点处添加的点，避免箭头长度大于两点距离时出现细微裂缝（未严格测试是否影响其它情况）
 * 210526：.linePrecision has been deprecated. Use .params.Line.threshold instead.
 * 210824：升级修改【three.js r120-r131】
 */
import * as THREE from 'three';
import Props from "../general/props";

const ZERO_ARRAY=[new THREE.Vector3(0,0,0),new THREE.Vector3(1e-6,0,0)];

/**
 * 厚度线（填充块拐角）
 * 拐角处uv不连续
 * 支持箭头，虚线
 *
 @param vertices {array} 点集长度不能低于2，否则会用默认值(x:0到1e-6)代替
 @param values {object} {name,value}
 color:new THREE.Color(0,1,0),  颜色
 width:0.5,  线宽
 dashSize:0.1,  虚线长
 gapSize:0.1,  虚线空白长
 perspectiveFactor:10,  透视系数
 arrowLengthRat:1,  箭头长度比值（相对虚线+空白）
 arrowWidthRat:1,  箭头宽比（相对线宽）
 arrowMode:0,  箭头模式[0:none,1:right,2:left,3:double]
 opacity:1,  透明度
 dashFill:true,  虚线还是实线
 sizeAttenuation:true,  是否启用近大远小
 zOffset:0,  z偏移

 材质通用属性可获取material进行编辑
 几何体通用属性可获取geometry进行编辑
 */
export default class HardLine extends THREE.Object3D{
    constructor(vertices,values={}){
        super();
        console.warn('this class is deprecated now ,user ThickLine instead!');
        this.isMesh=true;
        this.drawMode=THREE.TrianglesDrawMode;
        this.geometry=new THREE.BufferGeometry();
        this.material=new THREE.ShaderMaterial();
        this._verts=!Array.isArray(vertices)?ZERO_ARRAY:(vertices.length===0?ZERO_ARRAY:vertices);
        this._values=values;
        this._init();
    }

    //----------------------------------------对外属性及方法------------------------------------

    /**
     * 获取属性
     * @returns {null|props}
     */
    get props(){
        return this._props;
    }

    /**
     * 设置属性
     * @param values {object} {name,value}
     */
    set props(values){
        for(let key in values){
            if(this._props[''+key+'']){
                this._props[''+key+''].value=values[''+key+''];
            }else{
                console.warn(''+key+' is not a prop of HardLine!');
            }
        }
    }

    /**
     * 顶点
     * @returns {Array}
     */
    get vertices(){
        return this._verts;
    }

    /**
     *更新顶点
     * @param vs {Array}
     */
    set vertices(vs){
        this._verts=vs;
        this.updateGeometry();
    }

    /**
     * 设置箭头模式
     * @param mode {Number} 0:none,1:right,2:left,3:double
     */
    set arrowMode(mode){
        this._props.arrowMode.value=mode;
    }
    get arrowMode(){
        return this._props.arrowMode.value;
    }

    /**
     * 设置箭头宽度（与线宽的比值）
     * @param ratio {Number}
     */
    set arrowWidthRat(ratio){
        this._props.arrowWidthRat.value=ratio;
    }
    get arrowWidthRat(){
        return this._props.arrowWidthRat.value;
    }

    /**
     * 设置箭头长度（与虚线片段+空白的比值）
     * @param ratio {Number}
     */
    set arrowLengthRat(ratio){
        this._props.arrowLengthRat.value=ratio;
    }
    get arrowLengthRat(){
        return this._props.arrowLengthRat.value;
    }

    /**
     * 是否启用近大远小
     * @param bol {Boolean} true:启用 false:禁用
     */
    set sizeAttenuation(bol){
        this._props.sizeAttenuation.value=bol;
    }
    get sizeAttenuation(){
        return this._props.sizeAttenuation.value;
    }

    /**
     * 深度偏移值（深度很近时可设置显示层级）
     * @param offset {Number}
     */
    set zOffset(offset){
        this._props.zOffset.value=offset;
    }
    get zOffset(){
        return this.props.zOffset;
    }

    /**
     * 是否填充虚线空白（控制虚线还是实线）
     * @param bol {Boolean} true:实线 false:虚线
     */
    set dashFill(bol){
        this._props.dashFill.value=bol;
    }
    get dashFill(){
        return this._props.dashFill.value;
    }

    /**
     * 不透明度（0-1）
     * @param opacity {Number}
     */
    set opacity(opacity){
        this._props.opacity.value=opacity;
    }
    get opacity(){
        return this._props.opacity.value;
    }

    /**
     * 透视系数（不常用）
     * @param factor {Number}
     */
    set perspectiveFactor(factor){
        this._props.perspectiveFactor.value=factor;
    }
    get perspectiveFactor(){
        return this._props.perspectiveFactor.value;
    }

    /**
     * 虚线空白长度
     * @param size {Number}
     */
    set gapSize(size){
        this._props.gapSize.value=size;
    }
    get gapSize(){
        return this._props.gapSize.value;
    }

    /**
     * 虚线线段长度
     * @param size {Number}
     */
    set dashSize(size){
        this._props.dashSize.value=size;
    }
    get dashSize(){
        return this._props.dashSize.value;
    }

    /**
     * 线宽
     * @param width {Number}
     */
    set width(width){
        this._props.width.value=width;
    }
    get width(){
        return this._props.width.value;
    }

    /**
     * 颜色
     * @param color {Color} color:THREE.Color
     */
    set color(color){
        this._props.color.value=color;
    }
    get color(){
        return this._props.color.value;
    }

    /**
     * 长度
     * @returns {Number}
     */
    get length(){
        return this._distances[(this._rectifyVerts.length*2-3)*6-1];
    }

    /**
     * 顶点着色器
     * @returns {string}
     */
    get vertexShader(){
        return [
            'attribute vec3 nextPosition;',
            'attribute float offset;',
            'attribute float distance;' ,
            'attribute float dashMark;',
            'uniform float width;',
            'uniform float perspectiveFactor;' ,
            'uniform bool sizeAttenuation;' ,
            'uniform float zOffset;',
            'varying float vDistance;' ,
            'varying float vDashMark;',
            'varying vec2 vuv;',
            'float computeAspect(){' ,
            '   float aspect=0.5625;' ,
            '   if(projectionMatrix[3][3]==0.0){' ,
            '      float _tan=projectionMatrix[1][1];' ,
            '      float as_tan=1.0/projectionMatrix[0][0];' ,
            '      aspect=1.0/(as_tan*_tan);' ,
            '   }else{' ,
            '      float _height=1.0/projectionMatrix[1][1];' ,
            '      float _width=1.0/projectionMatrix[0][0];' ,
            '      aspect=_height/_width;' ,
            '   }' ,
            '   return aspect;' ,
            '}',
            'void main(){',
            '   vec4 mvPosition=modelViewMatrix*vec4(position,1.0);',
            '   vec4 pmvPosition=projectionMatrix*mvPosition;' ,
            '   vec4 mvNextPosition=modelViewMatrix*vec4(nextPosition,1.0);',
            '   vec4 nxtP=projectionMatrix*mvNextPosition;',
            '   vec4 curP=pmvPosition;',
            '   vec3 nxt=vec3(nxtP.xyz/nxtP.w);',
            '   vec3 cur=vec3(curP.xyz/curP.w);',
            '   vec3 dirOri=nxt-cur;',
            '   vec3 look=vec3(0.0,0.0,1.0);',

            '   vec3 dirCC=mvPosition.xyz/mvPosition.w;' ,
            '   vec3 dirCN=mvNextPosition.xyz/mvNextPosition.w;' ,
            '   float dotC=dot(look,dirCC);' ,
            '   float dotN=dot(look,dirCN);' ,
            '   if(dotC*dotN<0.){' ,
            '      if(projectionMatrix[3][3]==0.0){' ,
            '           look=vec3(0.,0.,-1.);' ,
            '       }' ,
            '   }' ,

            '   float aspect=computeAspect();' ,
            '   dirOri.y*=aspect;',
            '   vec3 crs=cross(look,dirOri);',
            '   vec3 dir=normalize(crs);',
            '   dir.x*=aspect;',
            '   float rad=width;' ,
            '   pmvPosition.z+=zOffset;' ,
            '   if(sizeAttenuation){' ,
            '       rad*=projectionMatrix[1][1];' ,
            '      gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset,pmvPosition.w);' ,
            '   }else{' ,
            '      gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset*pmvPosition.w,pmvPosition.w);',
            '   }',
            '   vDistance=distance;' ,
            '   vDashMark=dashMark;',
            '   vuv=vec2(distance,sign(offset)*0.5);',
            '}'
        ].join('\n');
    }

    /**
     * 片元着色器
     * @returns {string}
     */
    get fragmentShader(){
        return [
            'uniform float dashSize;' ,
            'uniform float gapSize;',
            'uniform float opacity;' ,
            'uniform bool dashFill;',
            'uniform vec3 color;',
            'varying float vDistance;',
            'varying float vDashMark;',
            'varying vec2 vuv;',
            'void main(){',
            '   if(dashFill||vDashMark==0.){',
            '      gl_FragColor=vec4(color,opacity);',
            '   }else{',
            '      float dev=mod(vDistance,dashSize+gapSize);',
            '      if(dev>dashSize){',
            '         discard;',
            '      }',
            '      gl_FragColor=vec4(color,opacity);',
            '   }',
            '}'
        ].join('\n');
    }

    /**
     * 计算点到向量（线）的距离（二次方）和最近点
     * @param dv {Vector3} 用于计算的变量
     * @param de {Vector3} 用于计算的变量
     * @param v {Vector3} 目标点
     * @param vs {Vector3} 向量起点
     * @param ve {Vector3} 向量终点
     * @param point {Vector3} 最近点结果输出
     * @returns {number} 距离（二次方）
     */
    static distanceOfPointToVector(dv,de,v,vs,ve,point){
        dv.subVectors(v,vs);
        de.subVectors(ve,vs);
        dv.projectOnVector(de).add(vs);
        point.copy(dv);
        return v.distanceToSquared(dv);
    }

    /**
     * 计算包围球
     */
    computeBoundingSphere(){
        if(!this.geometry.boundingSphere)this.geometry.boundingSphere=new THREE.Sphere();
        this.geometry.boundingSphere.setFromPoints(this._verts);
    }

    /**
     * 更新顶点之间的距离（影响虚线）
     */
    updateDistance(){
        let pushData=(i1,d)=>{
            this._distances[i1]=d;
        };
        let d0=0;
        let d1=0;
        for(let i=0;i<this._rectifyVerts.length-1;i++){
            let cp=i===0?this._tV0.copy(this._rectifyVerts[i]).applyMatrix4(this.matrixWorld):this._tV2.copy(this._tV1);
            let np=this._tV1.copy(this._rectifyVerts[i+1]).applyMatrix4(this.matrixWorld);
            if(i===0){
                d1=cp.distanceTo(np);
                pushData(0,d0);
                pushData(1,d0);
                pushData(2,d1);
                pushData(3,d0);
                pushData(4,d1);
                pushData(5,d1);
                d0=d1;
            }else{
                let i1=6+(i-1)*12;
                pushData(i1,d0);
                pushData(i1+1,d0);
                pushData(i1+2,d0);
                pushData(i1+3,d0);
                pushData(i1+4,d0);
                pushData(i1+5,d0);
                //
                d1+=cp.distanceTo(np);
                pushData(i1+6,d0);
                pushData(i1+7,d0);
                pushData(i1+8,d1);
                pushData(i1+9,d0);
                pushData(i1+10,d1);
                pushData(i1+11,d1);
                d0=d1;
            }
        }
        this.geometry.getAttribute('distance').copyArray(this._distances);
        this.geometry.getAttribute('distance').needsUpdate=true;
    }

    /**
     * 更新几何体
     */
    updateGeometry() {
        let len = this._verts.length;
        if (len < 2) {
            this._verts=ZERO_ARRAY;
        }
        this._getData();
        this.geometry.getAttribute('position').copyArray(this._positions);
        this.geometry.getAttribute('nextPosition').copyArray(this._nextPositions);
        this.geometry.getAttribute('offset').copyArray(this._offsets);
        this.geometry.getAttribute('dashMark').copyArray(this._dashMarks);
        this.updateDistance();
        this.geometry.getAttribute('position').needsUpdate = true;
        this.geometry.getAttribute('nextPosition').needsUpdate = true;
        this.geometry.getAttribute('offset').needsUpdate = true;
        this.geometry.getAttribute('dashMark').needsUpdate = true;
        this.geometry.setDrawRange(0, ((this._rectifyVerts.length) * 2 - 3) * 6);
    }

    /**
     * 销毁释放
     */
    dispose(){
        this.geometry.dispose();
        this.material.dispose();
        this._props.dispose();
        this._positions=null;
        this._nextPositions=null;
        this._offsets=null;
        this._distances=null;
        this._dashMarks=null;

        this._tS=null;
        this._arrowWidth=null;
        this._arrowLength=null;
        this._rectifyVerts=null;
        this._tV0=null;
        this._tV1=null;
        this._tV2=null;
        this._tV3=null;
        this._tM=null;
        this._tR=null;

        this._props=null;
    }

    /**
     * 拷贝
     * @param source {HardLine}
     * @param recursive {boolean}
     */
    copy(source,recursive=false){
        super.copy(source,recursive);
        this.material.copy(source.material);
        this.material.needsUpdate=true;
        this.props=source.props.all(true);
        this.vertices=[...source.vertices];
        this.computeBoundingSphere();
        return this;
    }

    //----------------------------------------私有属性及方法---------------------------------------------

    _init(){
        this._initProps();
        this._initVariable();
        this._initGeometry();
        this._initMaterial();
        this._initRaycast();
        this._initPropCallback();
    }
    _initProps(){
        this._props=new Props({
            color:this._values.color||new THREE.Color(0,1,0),//颜色
            width:this._values.width||0.5,//线宽
            dashSize:this._values.dashSize!==undefined?this._values.dashSize:0.1,//虚线长
            gapSize:this._values.gapSize!==undefined?this._values.gapSize:0.1,//虚线空白长
            perspectiveFactor:this._values.perspectiveFactor||2,//透视系数
            arrowLengthRat:this._values.arrowLengthRat||1,//箭头长度比值（相对虚线+空白）
            arrowWidthRat:this._values.arrowWidthRat!==undefined?this._values.arrowWidthRat:1,//箭头宽比（相对线宽）
            arrowMode:this._values.arrowMode!==undefined?this._values.arrowMode:0,//箭头模式[0:none,1:right,2:left,3:double]
            opacity:this._values.opacity!==undefined?this._values.opacity:1,//透明度
            dashFill:this._values.dashFill!==undefined?this._values.dashFill:true,//虚线还是实线
            sizeAttenuation:this._values.sizeAttenuation!==undefined?this._values.sizeAttenuation:true,//是否启用近大远小
            zOffset:this._values.zOffset!==undefined?this._values.zOffset:0,//z偏移
        });
    }
    _initPropCallback(){
        this._props.onMultiKeyChange(()=>{
            this.updateGeometry();
        },'arrowMode','arrowWidthRat','arrowLengthRat');
        this._props.onKeyChange('sizeAttenuation',(_new,_old)=>{
            this.material.uniforms.sizeAttenuation.value=_new;
        });
        this._props.onKeyChange('zOffset',(_new,_old)=>{
            this.material.uniforms.zOffset.value=_new;
        });
        this._props.onKeyChange('dashFill',(_new,_old)=>{
            this.material.uniforms.dashFill.value=_new;
        });
        this._props.onKeyChange('opacity',(_new,_old)=>{
            this.material.uniforms.opacity.value=_new;
        });
        this._props.onKeyChange('perspectiveFactor',(_new,_old)=>{
            this.material.uniforms.perspectiveFactor.value=_new;
        });
        this._props.onKeyChange('gapSize',(_new,_old)=>{
            this.updateGeometry();
            this.material.uniforms.gapSize.value=_new;
        });
        this._props.onKeyChange('dashSize',(_new,_old)=>{
            this.updateGeometry();
            this.material.uniforms.dashSize.value=_new;
        });
        this._props.onKeyChange('width',(_new,_old)=>{
            this.material.uniforms.width.value=_new;
        });
        this._props.onKeyChange('color',(_new,_old)=>{
            this.material.uniforms.color.value=_new;
        });
    }
    _initVariable(){
        this._maxSegments=this._verts.length*2;

        //segments:(2n-3)*6*3
        let s=this._maxSegments;
        let s3=(2*s-3)*6*3;
        let s1=(2*s-3)*6;
        this._positions=new Float32Array(s3);
        this._nextPositions=new Float32Array(s3);
        this._offsets=new Int8Array(s1);
        this._distances=new Float32Array(s1);
        this._dashMarks=new Uint8Array(s1);
        this._tS=new THREE.Sphere();
        this._arrowWidth=0;
        this._arrowLength=0;
        this._rectifyVerts=[];
        this._tV0=new THREE.Vector3();
        this._tV1=new THREE.Vector3();
        this._tV2=new THREE.Vector3();
        this._tV3=new THREE.Vector3();
        this._tM=new THREE.Matrix4();
        this._tR=new THREE.Ray();
    }
    _setAttribute(){
        this.geometry.setAttribute('position',new THREE.Float32BufferAttribute(this._positions,3));
        this.geometry.setAttribute('nextPosition',new THREE.Float32BufferAttribute(this._nextPositions,3));
        this.geometry.setAttribute('offset',new THREE.Int8BufferAttribute(this._offsets,1));
        this.geometry.setAttribute('dashMark',new THREE.Uint8BufferAttribute(this._dashMarks,1));
        this.geometry.setAttribute('distance',new THREE.Float32BufferAttribute(this._distances,1));
    }
    _initGeometry(){
        this._getData();
        this._setAttribute();
        this.updateDistance();
        this.geometry.setDrawRange(0,(this._rectifyVerts.length*2-3)*6);
    }
    _rectifyPoints(ps) {
        switch (this._props.arrowMode.value) {
            case 0:
                this._arrowWidth=0;
                this._arrowLength=0;
                return ps;
            case 1:
                return this._rectifyRightArrowPoints(ps);
            case 2:
                return this._rectifyLeftArrowPoints(ps);
            case 3:
                return this._rectifyDoubleArrowPoints(ps);
        }
    }
    _rectifyDoubleArrowPoints(ps){
        let temp=[];
        this._arrowWidth =1 + 2 * this._props.arrowWidthRat.value;
        let dAg=this._props.dashSize.value + this._props.gapSize.value;
        this._arrowLength = dAg * this._props.arrowLengthRat.value;
        let indexLeft=-1;
        let indexRight=-1;
        let iStartLeft=-1;
        let iStartRight=-1;
        let tLen=0;
        for (let i = 0; i <ps.length-1; i++) {
            tLen += ps[i].distanceTo(ps[i + 1]);
            if(tLen>0)iStartLeft=i+1;
            if (tLen >= this._arrowLength) {
                indexLeft = i + 1;
                break;
            }
        }
        tLen=0;
        for (let i = ps.length - 1; i > 0; i--) {
            tLen += ps[i].distanceTo(ps[i - 1]);
            if(tLen>0)iStartRight=i-1;
            if (tLen >= this._arrowLength) {
                indexRight = i - 1;
                break;
            }
        }
        if(iStartRight===-1||iStartLeft===-1){
            temp=ps.slice(0);
            this._arrowWidth=0;
            this._arrowLength=0;
        }else{
            if(indexLeft===-1||indexRight===-1){
                this._arrowLength=tLen;
                let dirESL = this._tV0.subVectors(ps[iStartLeft], ps[0]).normalize();
                let nStartL = new THREE.Vector3().addVectors(ps[0], dirESL.multiplyScalar(this._arrowLength));
                temp.push(ps[0],nStartL,nStartL);
                let dirESR = this._tV1.subVectors(ps[iStartRight], ps[ps.length - 1]).normalize();
                let nStartR = new THREE.Vector3().addVectors(ps[ps.length - 1], dirESR.multiplyScalar(this._arrowLength));
                temp.push(nStartR,nStartR, ps[ps.length - 1]);
            }else{
                let dpLP=new THREE.Vector3();
                let dpL=HardLine.distanceOfPointToVector(this._tV0,this._tV1,ps[0],ps[indexLeft],ps[indexLeft-1],dpLP);
                let deL=Math.sqrt(this._arrowLength*this._arrowLength-dpL);
                let dirESL=this._tV0.subVectors(ps[indexLeft],ps[indexLeft-1]).normalize();
                let nStartL=dpLP.add(dirESL.multiplyScalar(deL));
                //
                let dpRP=new THREE.Vector3();
                let dpR=HardLine.distanceOfPointToVector(this._tV0,this._tV1,ps[ps.length-1],ps[indexRight],ps[indexRight+1],dpRP);
                let deR=Math.sqrt(this._arrowLength*this._arrowLength-dpR);
                let dirESR=this._tV0.subVectors(ps[indexRight],ps[indexRight+1]).normalize();
                let nStartR=dpRP.add(dirESR.multiplyScalar(deR));

                // temp.push(ps[0],nStartL,nStartL);20.2.27
                temp.push(ps[0],nStartL);
                for(let i=indexLeft;i<indexRight+1;i++){
                    temp.push(ps[i]);
                }
                // temp.push(nStartR,nStartR,ps[ps.length-1]);20.2.27
                temp.push(nStartR,ps[ps.length-1]);
            }
        }
        return temp;
    }
    _rectifyLeftArrowPoints(ps){
        let temp = [];
        this._arrowWidth = 1 + 2 * this._props.arrowWidthRat.value;
        let dAg=this._props.dashSize.value + this._props.gapSize.value;
        this._arrowLength = dAg * this._props.arrowLengthRat.value;
        let index = -1;
        let iStart=-1;
        let tLen=0;
        for (let i = 0; i <ps.length-1; i++) {
            tLen += ps[i].distanceTo(ps[i + 1]);
            if(tLen>0)iStart=i+1;
            if (tLen >= this._arrowLength) {
                index = i + 1;
                break;
            }
        }
        if(iStart===-1){
            temp=ps.slice(0);
            this._arrowWidth=0;
            this._arrowLength=0;
        }else {
            if (index === -1) {
                this._arrowLength=tLen;
                let dirES = this._tV0.subVectors(ps[iStart], ps[0]).normalize();
                let nStart = new THREE.Vector3().addVectors(ps[0], dirES.multiplyScalar(this._arrowLength));
                temp.push(ps[0],nStart,nStart);
            } else {
                let dpP=new THREE.Vector3();
                let dp=HardLine.distanceOfPointToVector(this._tV0,this._tV1,ps[0],ps[index],ps[index-1],dpP);
                let de=Math.sqrt(this._arrowLength*this._arrowLength-dp);
                let dirES=this._tV0.subVectors(ps[index],ps[index-1]).normalize();
                let nStart=dpP.add(dirES.multiplyScalar(de));
                // temp.push(ps[0],nStart,nStart);20.2.27
                temp.push(ps[0],nStart);
                for(let i=index;i<ps.length;i++){
                    temp.push(ps[i]);
                }
            }
        }
        return temp;
    }
    _rectifyRightArrowPoints(ps){
        let temp = [];
        this._arrowWidth = 1 + 2 * this._props.arrowWidthRat.value;
        let dAg=this._props.dashSize.value + this._props.gapSize.value;
        this._arrowLength = dAg * this._props.arrowLengthRat.value;
        let index = -1;
        let iStart=-1;
        let tLen=0;
        for (let i = ps.length - 1; i > 0; i--) {
            tLen += ps[i].distanceTo(ps[i-1]);
            if(tLen>0)iStart=i-1;
            if (tLen >= this._arrowLength) {
                index = i - 1;
                break;
            }
        }
        if(iStart===-1){
            temp=ps.slice(0);
            this._arrowWidth=0;
            this._arrowLength=0;
        }else {
            if (index === -1) {
                this._arrowLength=tLen;
                let dirES = this._tV0.subVectors(ps[iStart], ps[ps.length - 1]).normalize();
                let nStart = new THREE.Vector3().addVectors(ps[ps.length - 1], dirES.multiplyScalar(this._arrowLength));
                temp.push(nStart,nStart, ps[ps.length - 1]);
            } else {
                let dpP=new THREE.Vector3();
                let dp=HardLine.distanceOfPointToVector(this._tV0,this._tV1,ps[ps.length-1],ps[index],ps[index+1],dpP);
                let de=Math.sqrt(this._arrowLength*this._arrowLength-dp);
                let dirES=this._tV0.subVectors(ps[index],ps[index+1]).normalize();
                let nStart=dpP.add(dirES.multiplyScalar(de));
                for(let i=0;i<index+1;i++){
                    temp.push(ps[i]);
                }
                // temp.push(nStart,nStart,ps[ps.length-1]);20.2.27
                temp.push(nStart,ps[ps.length-1]);
            }
        }
        return temp;
    }
    _reRangeData(){
        let reRange=this._maxSegments<this._rectifyVerts.length;
        if(reRange){
            this.geometry.dispose();
            let s=this._rectifyVerts.length*2;
            this._maxSegments=s;
            let s3=(2*s-3)*6*3;
            let s1=(2*s-3)*6;
            this._positions=new Float32Array(s3);
            this._nextPositions=new Float32Array(s3);
            this._offsets=new Int8Array(s1);
            this._distances=new Float32Array(s1);
            this._dashMarks=new Uint8Array(s1);
            this._setAttribute();
        }
        return reRange;
    }
    _getData(){
        this._rectifyVerts=this._rectifyPoints(this._verts);
        if(this._rectifyVerts.length<2){
            console.warn('At least 2 points needed!');
            return;
        }
        this._reRangeData();
        switch (this._props.arrowMode.value){
            case 0:
                this._pushSolidData(this._rectifyVerts);
                break;
            case 1:
                if(this._arrowWidth===0||this._arrowLength===0){
                    this._pushSolidData(this._rectifyVerts);
                }else {
                    this._pushRightArrowData(this._rectifyVerts);
                }
                break;
            case 2:
                if(this._arrowWidth===0||this._arrowLength===0){
                    this._pushSolidData(this._rectifyVerts);
                }else {
                    this._pushLeftArrowData(this._rectifyVerts);
                }
                break;
            case 3:
                if(this._arrowWidth===0||this._arrowLength===0){
                    this._pushSolidData(this._rectifyVerts);
                }else {
                    this._pushDoubleArrowData(this._rectifyVerts);
                }
                break;
        }
    }
    _pushData(i3,i1,c,n,o,dm){
        this._offsets[i1]=o;
        this._dashMarks[i1]=dm;
        this._positions[i3]=c.x;
        this._positions[i3+1]=c.y;
        this._positions[i3+2]=c.z;
        this._nextPositions[i3]=n.x;
        this._nextPositions[i3+1]=n.y;
        this._nextPositions[i3+2]=n.z;
    }
    _pushSolidData(vs){
        let n0=this._tV0;
        let n1=this._tV1;
        for(let i=0;i<vs.length-1;i++){
            let cp=vs[i];
            let np=vs[i+1];
            if(i===0){
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(0,0,cp,n0,1,1);
                this._pushData(3,1,cp,n0,-1,1);
                this._pushData(6,2,np,n1,1,1);
                this._pushData(9,3,cp,n0,-1,1);
                this._pushData(12,4,np,n1,-1,1);
                this._pushData(15,5,np,n1,1,1);
            }else{
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,1,1);
                this._pushData(i3+21,i1+7,cp,n0,-1,1);
                this._pushData(i3+24,i1+8,np,n1,1,1);
                this._pushData(i3+27,i1+9,cp,n0,-1,1);
                this._pushData(i3+30,i1+10,np,n1,-1,1);
                this._pushData(i3+33,i1+11,np,n1,1,1);
            }
        }
    }
    _pushRightArrowData(vs){
        let n0=this._tV0;
        let n1=this._tV1;
        for(let i=0;i<vs.length-1;i++){
            let cp=vs[i];
            let np=vs[i+1];
            if(i===0){
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(0,0,cp,n0,1,1);
                this._pushData(3,1,cp,n0,-1,1);
                this._pushData(6,2,np,n1,1,1);
                this._pushData(9,3,cp,n0,-1,1);
                this._pushData(12,4,np,n1,-1,1);
                this._pushData(15,5,np,n1,1,1);
            }else if(i===vs.length-2){
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,this._arrowWidth,0);
                this._pushData(i3+21,i1+7,cp,n0,-this._arrowWidth,0);
                this._pushData(i3+24,i1+8,np,n1,0,0);
                this._pushData(i3+27,i1+9,cp,n0,-this._arrowWidth,0);
                this._pushData(i3+30,i1+10,np,n1,0,0);
                this._pushData(i3+33,i1+11,np,n1,0,0);
            }else{
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,1,1);
                this._pushData(i3+21,i1+7,cp,n0,-1,1);
                this._pushData(i3+24,i1+8,np,n1,1,1);
                this._pushData(i3+27,i1+9,cp,n0,-1,1);
                this._pushData(i3+30,i1+10,np,n1,-1,1);
                this._pushData(i3+33,i1+11,np,n1,1,1);
            }
        }
    }
    _pushLeftArrowData(vs){
        let n0=this._tV0;
        let n1=this._tV1;
        for(let i=0;i<vs.length-1;i++){
            let cp=vs[i];
            let np=vs[i+1];
            if(i===0){
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(0,0,cp,n0,0,0);
                this._pushData(3,1,cp,n0,0,0);
                this._pushData(6,2,np,n1,this._arrowWidth,0);
                this._pushData(9,3,cp,n0,0,0);
                this._pushData(12,4,np,n1,-this._arrowWidth,0);
                this._pushData(15,5,np,n1,this._arrowWidth,0);
            }else{
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,1,1);
                this._pushData(i3+21,i1+7,cp,n0,-1,1);
                this._pushData(i3+24,i1+8,np,n1,1,1);
                this._pushData(i3+27,i1+9,cp,n0,-1,1);
                this._pushData(i3+30,i1+10,np,n1,-1,1);
                this._pushData(i3+33,i1+11,np,n1,1,1);
            }
        }
    }
    _pushDoubleArrowData(vs){
        let n0=this._tV0;
        let n1=this._tV1;
        for(let i=0;i<vs.length-1;i++){
            let cp=vs[i];
            let np=vs[i+1];
            if(i===0){
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(0,0,cp,n0,0,0);
                this._pushData(3,1,cp,n0,0,0);
                this._pushData(6,2,np,n1,this._arrowWidth,0);
                this._pushData(9,3,cp,n0,0,0);
                this._pushData(12,4,np,n1,-this._arrowWidth,0);
                this._pushData(15,5,np,n1,this._arrowWidth,0);
            }else if(i===vs.length-2){
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,this._arrowWidth,0);
                this._pushData(i3+21,i1+7,cp,n0,-this._arrowWidth,0);
                this._pushData(i3+24,i1+8,np,n1,0,0);
                this._pushData(i3+27,i1+9,cp,n0,-this._arrowWidth,0);
                this._pushData(i3+30,i1+10,np,n1,0,0);
                this._pushData(i3+33,i1+11,np,n1,0,0);
            }else{
                let i3=18+(i-1)*36;
                let i1=6+(i-1)*12;
                n0.subVectors(cp,vs[i-1]).normalize().add(cp);
                n1.copy(np);
                this._pushData(i3,i1,cp,n0,1,1);
                this._pushData(i3+3,i1+1,cp,n0,0,1);
                this._pushData(i3+6,i1+2,cp,n1,1,1);
                this._pushData(i3+9,i1+3,cp,n0,-1,1);
                this._pushData(i3+12,i1+4,cp,n1,-1,1);
                this._pushData(i3+15,i1+5,cp,n1,0,1);
                //
                n0.copy(np);
                n1.subVectors(np,cp).normalize().add(np);
                this._pushData(i3+18,i1+6,cp,n0,1,1);
                this._pushData(i3+21,i1+7,cp,n0,-1,1);
                this._pushData(i3+24,i1+8,np,n1,1,1);
                this._pushData(i3+27,i1+9,cp,n0,-1,1);
                this._pushData(i3+30,i1+10,np,n1,-1,1);
                this._pushData(i3+33,i1+11,np,n1,1,1);
            }
        }
    }
    _initMaterial(){
        this.material.vertexShader=this.vertexShader;
        this.material.fragmentShader=this.fragmentShader;
        this.material.uniforms={
            color:{value:this._props.color.value},
            width:{value:this._props.width.value},
            dashSize:{value:this._props.dashSize.value},
            perspectiveFactor:{value:this._props.perspectiveFactor.value},
            gapSize:{value:this._props.gapSize.value},
            opacity:{value:this._props.opacity.value},
            dashFill:{value:this._props.dashFill.value},
            sizeAttenuation:{value:this._props.sizeAttenuation.value},
            zOffset:{value:this._props.zOffset.value},
        };
        this.material.transparent=true;
        this.material.depthTest=true;
        this.material.needsUpdate=true;
    }
    _initRaycast(){
        this.raycast=(()=>{
            let inverseMatrix = this._tM;
            let ray = this._tR;
            let sphere = this._tS;
            return ( raycaster, intersects )=> {
                /**
                 * 21.5.26
                 */
                // let precision = raycaster.linePrecision;
                let precision = raycaster.params.Line.threshold;

                let precisionSq = precision * precision;
                let matrixWorld = this.matrixWorld;
                if(this.geometry.boundingSphere===null)this.computeBoundingSphere();
                sphere.copy(this.geometry.boundingSphere);
                sphere.applyMatrix4(matrixWorld);
                if (raycaster.ray.intersectsSphere(sphere) === false) return;
                // inverseMatrix.getInverse(matrixWorld);20210824
                inverseMatrix.copy(matrixWorld).invert();
                ray.copy(raycaster.ray).applyMatrix4(inverseMatrix);
                let vStart = this._tV0;
                let vEnd = this._tV1;
                let interSegment = this._tV2;
                let interRay = this._tV3;
                for (let i = 0, l = this._verts.length - 1; i < l; i += 1) {
                    vStart.copy(this._verts[i]);
                    vEnd.copy(this._verts[i+1]);
                    let distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment);
                    if (distSq > precisionSq) continue;
                    interRay.applyMatrix4(this.matrixWorld);
                    let distance = raycaster.ray.origin.distanceTo(interRay);
                    if (distance < raycaster.near || distance > raycaster.far) continue;
                    intersects.push({
                        distance: distance,
                        point: interSegment.clone().applyMatrix4(this.matrixWorld),
                        index: i,
                        face: null,
                        faceIndex: null,
                        object: this
                    });
                }
            };
        })();
    }
}


